[機能アップデート]ついに Lambda@Edge が HTTP リクエストボディにアクセスできるようになった!
AWS 本家ブログ記事にて、Lambda@Edge で HTTP リクエストの BODY にアクセスできることがアナウンスされました。
Today we announced that Lambda@Edge can now access the HTTP Request Body.
これまで Lambda@Edge ではヘッダのみに限定されていましたので、これは大きなアップデートですね!早速、本家記事を参考に、サンプルケースを試してみましょう!
どんなことが出来るの?
サンプルケースでは、CloudFront + Lambda@Edge で受けたリクエストボディを Kinesis Firehose で S3 に配信する内容となっています。 一般的に、Kinesis にデータを送信する場合、SDK か Kinesis Agent を使用します。もしくは HTTP で POST する場合、API Gateway を Kinesis プロキシとして配置し、 Lambda で流す必要がありました。今回のアップデートにより、Kinesis プロキシを CloudFront + Lambda@Edge だけでも出来るようになったということですね!素敵ですね!
ではさっそくやってみる
CloudFront ディストリビューションの作成
CloudFront の管理画面から、ディストリビューションを作成します。
[Web]の下にある、[Get Started]をクリックします。
ディストリビューションのオプションを選択します。今回は検証目的ですので、必要なところ以外はデフォルトで設定します。
- POST メソッドを受けますので [Allowed HTTP Methods]は
GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
を選択 - [Cache Based on Selected Request Headers]は
ALL
を選択- POST については元々キャシュされずにパススルーされますが、一旦
ALL
にしました
- POST については元々キャシュされずにパススルーされますが、一旦
その他はデフォルトのまま [Create Distribution]をクリックし作成します。
[Status]が Deployed
、および[State]が Enabled
に変わるまで数分待ちますので、その間に次の手順に進みましょう。
Kinesis Firehose 配信ストリームの作成
次に Kinesis Firehose の配信ストリームを作成します。実際の用途であれば、ユーザがいるすべてのリージョンに作成し、CloudFront のエッジロケーションから最も近い配信ストリームに送ることでレイテンシを下げるべきかと思いますが、今回は検証目的ですので東京リージョンのみに作成します。
Kinesis のナビゲート画面から[配信ストリームの作成]をクリックします。
[Step 1:Name and source]では、[Delivery stream name]に任意の名前を割り当て、[Source]では Direct PUT or other sources
を選択し、[Next]をクリックします。
[Step 2:Process records]はデフォルトのまま[Next]をクリックします。
[Step 3:Choose destination]では、[Destination]で Amazon S3
を選択し、データを保存するためのバケットを選択(または作成)します。必要に応じてプレフィックスの指定も可能です。今回は東京リージョンの S3 バケットを指定しました。設定できましたら[Next]をクリックします。
[Step 4:Configure settings]では、バッファサイズとインターバルの設定が可能ですが、今回はデフォルト設定にしています。また、IAM ロールを作成(または選択)します。検証の場合、この画面から新たに作成した方が、必要な権限(対象バケットへの PUT 権限など)の選択などが不要なので楽でしょう。作成できましたら [Next] をクリックします。
[Step 5:Review]で設定値を確認し、問題なければ[Create delivery stream]をクリックし、配信ストリームを作成します。
各リージョンでも配信ストリームを作成される場合ですが、今回のサンプルコードはすべて同じ配信ストリーム名を想定した形になっていますので、ご注意ください。
Lambda@Edg 関数の作成
次に Lambda@Edge の関数を作成するので、リージョンをバージニア北部に切り替え Lambda の管理画面を開き、[関数の作成]をクリックします。[一から作成]を選択し、関数名は任意の名前を割り当て、ランタイムは [Node.js 6.10]を選択。ロールは以下のポリシーと、信頼関係をセットしたロールを選択(もしくは作成)します。
- AmazonKinesisFirehoseFullAccess
- AWSLambdaBasicExecutionRole
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "edgelambda.amazonaws.com", "lambda.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] }
Lambda コードはブログ記事より拝借し、コピペします。今回、単一リージョンにのみ Kinesis Firehose 配信ストリームを作成し利用しますので、4行目のコメントを解除し、5行目が不要になりますのでコメントアウトしています。また、今回は東京リージョンの配信ストリームを使いますので、ap-northeast-1
としました。また、7行目の DeliveryStreamName
は、作成された環境にあわせて配信ストリーム名を変更してください。
exports.handler = (event, context, callback) => { var bodyData = new Buffer(event.Records[0].cf.request.body.data, 'base64').toString("utf-8"); var AWS = require('aws-sdk'); var kinesisfh = new AWS.Firehose({region: 'ap-northeast-1'}); //uncomment if you want a specific-region of Firehose //var kinesisfh = new AWS.Firehose(); //or provision a firehose in every region where Lambda may run var params = { DeliveryStreamName: 'edgetest', /* required */ Record: { /* required */ Data: JSON.stringify({bodyData}) } }; var responseBody = "Successfully Submitted Record"; responseBody += bodyData; responseBody += context.invokedFunctionArn; responseBody += "Invoke Id: "; responseBody += context.invokeid; kinesisfh.putRecord(params, function(err, data) { if (err) console.log(err, err.stack); // an error occurred else console.log(responseBody); // successful response }); var headers = []; headers['strict-transport-security'] = [{ key: 'Strict-Transport-Security', value: "max-age=31536000; includeSubdomains; preload" }]; headers['content-security-policy'] = [{ key: 'Content-Security-Policy', value: "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'" }]; headers['x-content-type-options'] = [{ key: 'X-Content-Type-Options', value: "nosniff" }]; const response = { body: responseBody, bodyEncoding: 'text', headers, status: '200', statusDescription: 'OK' }; callback(null, response); return response; };
Lambda のリソース設定としては、タイムアウトをデフォルト3秒から5秒に変更し、画面右上の[保存]をクリックします。(処理の内容次第で、適宜変更してください)
次に[アクション]-[新しいバージョンを発行]をクリックし、バージョン名は任意に入力ください。(省略の場合、自動で割当たります)
トリガーの追加欄から[CloudFront]をクリックします。
[トリガーの設定]では、先程作成したディストリビューションを選択、キャッシュ動作は今回はデフォルトのままです。[CloudFront イベント]ではビューアーリクエストを選択します。そして、新しく追加された項目[ボディを含める](include body)にチェックをいれ、[トリガーとレプリケートの有効化]にもチェックを入れて、[追加]をクリックします。最後に画面右上にもどり[保存]をクリックします。
CloudFront ディストリビューションの[Status]が Deployed
、[State]が Enabled
に変わるまで数分待ちます。
では、動作を確認してみましょう!
動作を確認してみる!
それでは curl
コマンドで POST してみましょう!
$ curl http://XXXXXXXX.cloudfront.net --request POST --data '{"name":"test http"}' Successfully Submitted Record{"name":"test http"}arn:aws:lambda:ap-northeast-1:XXXXXXXXXXX:function:us-east-1.edge-kinesis-producer:3Invoke Id: 24803517-a0ef-11e8-86c6-e779169f30aa
指定した S3 バケットを確認してみると、ファイルが作成されていますので内容を確認してみます。
{"bodyData":"{\"name\":\"test http\"}"}
問題なく HTTP リクエストボディの内容が Lambda@Edge で Firehose → S3 に配信できてますね! HTTPS のアクセスも確認しておきましょう。
$ curl https://XXXXXXXX.cloudfront.net --request POST --data '{"name":"test https"}' Successfully Submitted Record{"name":"test https"}arn:aws:lambda:ap-northeast-1:XXXXXXXXXXX:function:us-east-1.edge-kinesis-producer:3Invoke Id: 76f28370-a0f0-11e8-b1d6-5f2dd9f8ef19
HTTPS の場合でも CloudFront で終端されて Lambda@Edge で処理されてるので問題なさそうですね!
{"bodyData":"{\"name\":\"test https\"}"}
制限
HTTP リクエストボディにアクセスするにあたって、いくつか制限があるので確認しておきましょう。
- ボディは常に Lambda@Edge によって base64 でエンコードされています
- 人が読める状態でボディを処理するには、base64 デコードの処理が必要です。(サンプルコード2行目がデコード処理になっています)
- ボディのサイズが大きすぎる場合、Lambda@Edge はボディを切り捨てます
- ビューアーリクエストは、 40KB で切り捨てられます
- オリジンリクエストは、 1MB で切り捨てられます
- リクエストボディを読み取り専用でアクセスすると、元の状態のリクエストボディがオリジンに戻されます。ただし、リクエストボディに変更を加えた場合、Lambda 関数から戻されるとき次のボディサイズの制限が適用されます。
エンコーディングのタイプ | ビューアーリクエストのボディサイズ制限 | オリジンリクエストのボディサイズ制限 |
---|---|---|
text | 40KB | 1MB |
base64 | 53.2KB | 1.33MB |
さいごに
サンプルケースのように Kinesis プロキシとして利用する場合、送信元の IP 制限なども CloudFront + WAF によって容易に構成することが出来ますね!サイズ制限や、base64 でエンコードされているなど、いくつかの制限はありますが、Lambda@Edge がリクエストボディにアクセス出来るようになったことで、またまたアーキテクチャの幅が広がりますね!どんどん試して使っていきましょう!
以上!大阪オフィスの丸毛(@marumo1981)でした!